﻿/*
 *  RacerLib - XNA Racing Game Level and Tile Management Library
 * 
 *  Copyright (C) 2009 Sebastian Schäfer
 *  
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License 
 *  along with this program; if not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.Runtime.Serialization;

namespace gdv.VCPR.Racer
{
    /// <summary>
    /// A tile
    /// </summary>
    [Serializable()]
    public class Tile : ISerializable
    {
        /// <summary>
        /// the type the tile represents
        /// </summary>
        public int Type { get; set; }
        /// <summary>
        /// the orientation/rotation of the tile
        /// </summary>
        public ETileOrientation Orientation { get; set; }

        /// <summary>
        /// the orientation/rotation of the tile
        /// </summary>
        public int IOrientation { get { return (int)Orientation; } set { Orientation = (ETileOrientation) value;} }

        /// <summary>
        /// Position of the tile along the X-axis
        /// </summary>
        public int X { get; set; }
        /// <summary>
        /// Position of the tile along the Y-axis
        /// </summary>
        public int Y { get; set; }
        /// <summary>
        /// Position of the tile along the Z-axis
        /// </summary>
        public int Z { get; set; }

        /// <summary>
        /// Is the tile driven reverse
        /// </summary>
        public bool Reverse { get; set; }

        /// <summary>
        /// Construct a new instance
        /// </summary>
        /// <param name="type">Type of tile</param>
        /// <param name="orientation">Orientation of tile</param>
        /// <param name="x">Position along the X-axis</param>
        /// <param name="y">Position along the Y-axis</param>
        /// <param name="z">Position along the Z-axis</param>
        public Tile(int type, ETileOrientation orientation, int x, int y, int z)
        {
            Type = type;
            Orientation = orientation;
            X = x;
            Y = y;
            Z = z;
            Reverse = false;
        }

        /// <summary>
        /// Construct a new instance
        /// </summary>
        /// <param name="x">Position along the X-axis</param>
        /// <param name="y">Position along the Y-axis</param>
        /// <param name="z">Position along the Z-axis</param>
        public Tile(int x, int y, int z)
            : this(0, 0, x, y, z)
        {
        }

        /// <summary>
        /// Construct a new instance @ origin
        /// </summary>
        public Tile()
            : this(0, 0, 0, 0, 0)
        {
        }

        /// <summary>
        /// Serialization constructor
        /// </summary>
        /// <param name="info">SerializationInfo</param>
        /// <param name="ctxt">StreamingContext</param>
        public Tile(SerializationInfo info, StreamingContext ctxt)
        {
            X = (int)info.GetValue("X", typeof(int));
            Y = (int)info.GetValue("Y", typeof(int));
            Z = (int)info.GetValue("Z", typeof(int));
            
            Type = (int)info.GetValue("Type", typeof(int));
            Orientation = (ETileOrientation)info.GetValue("Orientation", typeof(int));
            Reverse = false;
        }

        /// <summary>
        /// Create a flat copy
        /// </summary>
        /// <returns>new tile, same data</returns>
        public Tile Clone()
        {
            return new Tile(Type, Orientation, X, Y, Z);
        }

        /// <summary>
        /// Check if tile is at same position - only X and Y coordinates are compared!
        /// </summary>
        /// <param name="tile">the tile that's compared</param>
        /// <returns>true or false</returns>
        public bool SamePosition(Tile tile)
        {
            return ((X == tile.X) && (Y == tile.Y));
        }

        /// <summary>
        /// Move a tile
        /// </summary>
        /// <param name="x">value that is subtracted from current position</param>
        /// <param name="y">value that is subtracted from current position</param>
        public void Move(int x, int y)
        {
            this.X = this.X - x;
            this.Y = this.Y - y;
        }


        /// <summary>
        /// Rotate the Orientation clockwise: North -> East -> South -> West
        /// </summary>
        /// <param name="orientation">original orientation</param>
        /// <returns>rotated orientation</returns>
        public static ETileOrientation RotateOrientationCW(ETileOrientation orientation)
        {
            switch (orientation)
            {
                case ETileOrientation.North:
                    return ETileOrientation.East;
                case ETileOrientation.East:
                    return ETileOrientation.South;
                case ETileOrientation.South:
                    return ETileOrientation.West;
                case ETileOrientation.West:
                    return ETileOrientation.North;
                default:
                    return ETileOrientation.North;
            }
        }

        /// <summary>
        /// Rotate the Orientation counterclockwise: North -> West -> South -> East
        /// </summary>
        /// <param name="orientation">original orientation</param>
        /// <returns>rotated orientation</returns>
        public static ETileOrientation RotateOrientationCCW(ETileOrientation orientation)
        {
            switch (orientation)
            {
                case ETileOrientation.North:
                    return ETileOrientation.West;
                case ETileOrientation.East:
                    return ETileOrientation.North;
                case ETileOrientation.South:
                    return ETileOrientation.East;
                case ETileOrientation.West:
                    return ETileOrientation.South;
                default:
                    return ETileOrientation.North;
            }
        }

        /// <summary>
        /// Generate a human readable string representing the tile
        /// </summary>
        /// <returns>position and orientation</returns>
        public override string ToString()
        {
            return "Tile@ " + X + "/" + Y + "/" + Z + " --> " + Orientation;
        }

        #region Typeflags ... 
        private static int CreateMask(ETileTypeFlags flag)
        {
            return 1 << (int)flag;
        }

        /// <summary>
        /// Set the flag
        /// </summary>
        /// <param name="flag">flag to set</param>
        /// <param name="value">value to set</param>
        public void SetFlag(ETileTypeFlags flag, bool value)
        {
            if (value)
                SetFlagTrue(flag);
            else
                SetFlagFalse(flag);
        }

        /// <summary>
        /// Set the flag to false
        /// </summary>
        /// <param name="flag">flag to set</param>
        public void SetFlagFalse(ETileTypeFlags flag)
        {
            Type = (Type & ~CreateMask(flag));
        }

        /// <summary>
        /// Set the flag to true
        /// </summary>
        /// <param name="flag">flag to set</param>
        public void SetFlagTrue(ETileTypeFlags flag)
        {
            Type = (Type | CreateMask(flag));
        }

        /// <summary>
        /// Returns if the flag is set or not
        /// </summary>
        /// <param name="flag">The flag to contain</param>
        /// <returns>Returns true if set, false otherwise</returns>
        public bool HasFlag(ETileTypeFlags flag)
        {
            return (Type & CreateMask(flag)) > 0;
        }
        #endregion


        #region ISerializable Member
        /// <summary>
        /// Serialize Deconstructor
        /// </summary>
        /// <param name="info">SerializationInfo</param>
        /// <param name="context">StreamingContext</param>
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("X", X);
            info.AddValue("Y", Y);
            info.AddValue("Z", Z);

            info.AddValue("Type", Type);
            info.AddValue("Orientation", (int)Orientation);
        }

        #endregion
    }
}
